Components with hierarchy
Contents
Components with hierarchy#

You can define components Parametric cells (waveguides, bends, couplers) with basic input parameters (width, length, radius …) and reuse the PCells in more complex PCells.
[1]:
from functools import partial
import toolz
from gdsfactory.typings import ComponentSpec
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
2023-02-20 17:48:53.474 | INFO | gdsfactory.config:<module>:50 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 6.43.1
2023-02-20 17:48:54.378 | INFO | gdsfactory.technology.layer_views:__init__:785 - Importing LayerViews from YAML file: /home/runner/work/gdsfactory/gdsfactory/gdsfactory/generic_tech/layer_views.yaml.
2023-02-20 17:48:54.385 | INFO | gdsfactory.pdk:activate:206 - 'generic' PDK is now active
[2]:
@gf.cell
def bend_with_straight(
bend: ComponentSpec = gf.components.bend_euler,
straight: ComponentSpec = gf.components.straight,
) -> gf.Component:
c = gf.Component()
b = gf.get_component(bend)
s = gf.get_component(straight)
bref = c << b
sref = c << s
sref.connect("o2", bref.ports["o2"])
c.info["length"] = b.info["length"] + s.info["length"]
return c
c = bend_with_straight()
print(c.metadata['info']['length'])
c
26.637
bend_with_straight: uid c53b8465, ports [], references ['bend_euler_1', 'straight_1'], 0 polygons
ComponentSpec#
When defining a Parametric cell you can use other ComponentSpec as an argument. It can be a:
string: function name of a cell registered on the active PDK.
"bend_circular"dict:
dict(component='bend_circular', settings=dict(radius=20))function: Using
functools.partialyou can customize the default parameters of a function.
1. string#
[3]:
c = bend_with_straight(bend="bend_circular")
c
bend_with_straight_bend_840d811a: uid b1db8557, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
2. dict#
Lets customize the functions that we pass. For example, we want to increase the radius of the bend from the default 10um to 20um.
[4]:
c = bend_with_straight(bend=dict(component="bend_circular", settings=dict(radius=20)))
c
bend_with_straight_113977eb: uid 89dc019c, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
3. function#
Partial lets you define different default parameters for a function, so you can modify the settings for the child cells.
[5]:
c = bend_with_straight(bend=gf.partial(gf.components.bend_circular, radius=30))
c
bend_with_straight_e172a9e6: uid b55cf7c6, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
[6]:
bend20 = partial(gf.components.bend_circular, radius=20)
b = bend20()
b
bend_circular_radius20: uid f28bc79d, ports ['o1', 'o2'], references [], 1 polygons
[7]:
type(bend20)
<class 'functools.partial'>
[8]:
bend20.func.__name__
'bend_circular'
[9]:
bend20.keywords
{'radius': 20}
[10]:
b = bend_with_straight(bend=bend20)
print(b.metadata['info']['length'])
b
41.416
bend_with_straight_e917218d: uid 4319af97, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
[11]:
# You can still modify the bend to have any bend radius
b3 = bend20(radius=10)
b3
bend_circular_radius10: uid c25a94aa, ports ['o1', 'o2'], references [], 1 polygons
PDK custom fab#
You can define a new PDK by creating function that customize partial parameters of the generic functions.
Lets say that this PDK uses layer (41, 0) for the pads (instead of the layer used in the generic pad function).
You can also access functools.partial from gf.partial
[12]:
pad = gf.partial(gf.components.pad, layer=(41, 0))
[13]:
c = pad()
c
pad_layer41__0: uid 115f5d81, ports ['e1', 'e2', 'e3', 'e4', 'pad'], references ['compass_1'], 0 polygons
Composing functions#
You can combine more complex functions out of smaller functions.
Lets say that we want to add tapers and grating couplers to a wide waveguide.
[14]:
c1 = gf.components.straight()
c1
straight: uid 35b0a132, ports ['o1', 'o2'], references [], 1 polygons
[15]:
straight_wide = gf.partial(gf.components.straight, width=3)
c3 = straight_wide()
c3
straight_width3: uid 21b1940e, ports ['o1', 'o2'], references [], 1 polygons
[16]:
c1 = gf.components.straight(width=3)
c1
straight_width3: uid 21b1940e, ports ['o1', 'o2'], references [], 1 polygons
[17]:
c2 = gf.add_tapers(c1)
c2
straight_width3_add_tap_9ae57f22: uid 5b1d4496, ports ['o1', 'o2'], references ['taper_1', 'taper_2', 'straight_1'], 0 polygons
[18]:
c2.metadata_child['changed'] # You can still access the child metadata
{'width': 3}
[19]:
c3 = gf.routing.add_fiber_array(c2, with_loopback=False)
c3
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
[20]:
c3.metadata_child['changed'] # You can still access the child metadata
{'width': 3}
Lets do it with a single step thanks to toolz.pipe
[21]:
add_fiber_array = gf.partial(gf.routing.add_fiber_array, with_loopback=False)
add_tapers = gf.add_tapers
# pipe is more readable than the equivalent add_fiber_array(add_tapers(c1))
c3 = toolz.pipe(c1, add_tapers, add_fiber_array)
c3
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
we can even combine add_tapers and add_fiber_array thanks to toolz.compose or toolz.compose
For example:
[22]:
add_tapers_fiber_array = toolz.compose_left(add_tapers, add_fiber_array)
c4 = add_tapers_fiber_array(c1)
c4
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
is equivalent to
[23]:
c5 = add_fiber_array(add_tapers(c1))
c5
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
as well as equivalent to
[24]:
add_tapers_fiber_array = toolz.compose(add_fiber_array, add_tapers)
c6 = add_tapers_fiber_array(c1)
c6
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
or
[25]:
c7 = toolz.pipe(c1, add_tapers, add_fiber_array)
c7
straight_width3_add_tap_c79a46d8: uid def6f0de, ports ['opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o1', 'opt-grating_coupler_ellipti_dd7f7af4-straight_width3-o2'], references ['bend_euler_1', 'straight_1', 'straight_2', 'bend_euler_2', 'straight_3', 'straight_4', 'grating_coupler_elliptical_trenches_1', 'grating_coupler_elliptical_trenches_2', 'add_tapers_1'], 0 polygons
[26]:
c7.metadata_child['changed'] # You can still access the child metadata
{'width': 3}
[27]:
c7.metadata['child']['child']['name']
'straight_width3'
[28]:
c7.metadata['child']['child']['function_name']
'straight'
[29]:
c7.metadata['changed'].keys()
dict_keys(['component', 'with_loopback'])